package de.g18.it0a.jlaser.solution;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import de.g18.it0a.jlaser.domain.Fixture;
import de.g18.it0a.jlaser.domain.Line;
import de.g18.it0a.jlaser.domain.Position;
import de.g18.it0a.jlaser.domain.Room;
import de.g18.it0a.jlaser.domain.RoomConfiguration;
import de.g18.it0a.jlaser.domain.Tile;
import de.g18.it0a.jlaser.domain.Vetrine;
import de.g18.it0a.jlaser.exception.MalformedConfigurationException;
import de.g18.it0a.jlaser.factory.RoomFactory;
import de.g18.it0a.jlaser.util.LineUtil;


public class SolutionHelper {

    private static final Map<RoomConfiguration, SolutionHelper> instances;

    static {
        instances = new HashMap<RoomConfiguration, SolutionHelper>();
    }


    private final Vector<Line> invalidLines;
    private final Map<Line, Collection<Tile>> crossedTilesByLine;

    private Room basicRoom;
    private Set<Tile> tilesToBeCrossed;


    private SolutionHelper(RoomConfiguration aConfig) {
        invalidLines = new Vector<Line>();
        crossedTilesByLine = new HashMap<Line, Collection<Tile>>();
        try {
            basicRoom = RoomFactory.buildRoom(aConfig);
        } catch (MalformedConfigurationException e) {
            throw new IllegalArgumentException("Got invalid configuration to work with!\n" + aConfig, e);
        }
    }

    public static SolutionHelper getInstance(RoomConfiguration aConfig) {
        if (!instances.containsKey(aConfig)) {
            createInstance(aConfig);
        }
        return instances.get(aConfig);
    }

    private static synchronized void createInstance(RoomConfiguration aConfig) {
        if (!instances.containsKey(aConfig)) {
            instances.put(aConfig, new SolutionHelper(aConfig));
        }
    }

    public Set<Tile> getTilesToBeCrossed() {
        if (tilesToBeCrossed == null) {
            tilesToBeCrossed = new HashSet<Tile>();
            for (Tile tile : basicRoom.getTiles()) {
                if (!tile.containsSolidFixtures()) {
                    tilesToBeCrossed.add(tile);
                }
            }
        }
        return tilesToBeCrossed;
    }

    public boolean isValidConnection(Position aFirst, Position aSecond) {
        return isValidConnection(new Line(aFirst, aSecond));
    }

    public boolean isValidConnection(Line aLine) {
        if (invalidLines.contains(aLine)) {
            return false;
        }

        for (Tile tile : basicRoom.getTilesBetween(aLine.getFirstPosition(), aLine.getSecondPosition())) {
            if (!LineUtil.intersects(aLine, tile)) {
                continue;
            }

            for (Fixture fixture : tile.getSolidFixtures()) {
                Line relativLine = new Line(new Position(aLine.getFirstPosition().getX() - tile.getPosition().getX(),
                                                         aLine.getFirstPosition().getY() - tile.getPosition().getY()),
                                            new Position(aLine.getSecondPosition().getX() - tile.getPosition().getX(),
                                                    aLine.getSecondPosition().getY() - tile.getPosition().getY()));
                if (relativLine.getLength() == 0) {
                    continue;
                }
                if (fixture instanceof Vetrine) {
                    invalidLines.add(aLine);
                    return false;
                }
            }

            if (crossedTilesByLine.containsKey(aLine)) {
                crossedTilesByLine.get(aLine).add(tile);
            } else {
                Collection<Tile> crossedTiles = new HashSet<Tile>();
                crossedTiles.add(tile);
                crossedTilesByLine.put(aLine, crossedTiles);
            }
        }
        return true;
    }

    public Collection<Tile> getTilesCrossedBy(Line aLine) {
        if (!isValidConnection(aLine) || !crossedTilesByLine.containsKey(aLine)) {
            return new HashSet<Tile>();
        }
        return crossedTilesByLine.get(aLine);
    }
}